package com.zmj.sy.mom.srv.aps.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zmj.sy.mom.srv.aps.bean.entity.base.BaseEntity;
import com.zmj.sy.mom.srv.aps.bean.entity.order.GroupWorkRule;
import com.zmj.sy.mom.srv.aps.bean.entity.resource.*;
import com.zmj.sy.mom.srv.aps.bean.vo.groupworkrule.*;
import com.zmj.sy.mom.srv.aps.bean.vo.shift.ShiftListBreakPeriodResVo;
import com.zmj.sy.mom.srv.aps.bean.vo.shifttyperule.ShiftTeamRuleAddDetailReqVo;
import com.zmj.sy.mom.srv.aps.bean.vo.shifttyperule.ShiftTeamRuleAddReqVo;
import com.zmj.sy.mom.srv.aps.bean.vo.shifttyperule.ShiftTeamRuleRemoveReqVo;
import com.zmj.sy.mom.srv.aps.bean.vo.weekdayscheme.WeekdaySchemeTimeRangeItemResVo;
import com.zmj.sy.mom.srv.aps.bean.vo.weekdayscheme.WeekdaySchemeTimeRangeResVo;
import com.zmj.sy.mom.srv.aps.mapper.*;
import com.zmj.sy.mom.srv.aps.utils.MkBeanUtils;
import com.zmj.sy.mom.srv.aps.utils.ProjectConstants;
import com.zmj.sy.mom.srv.aps.utils.SyExceptionUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@RequiredArgsConstructor
@Service
public class GroupWorkRuleService {

    private final GroupWorkRuleMapper groupWorkRuleMapper;
    private final GroupMapper groupMapper;
    private final ShiftMapper shiftMapper;
    private final BreakPeriodMapper breakPeriodMapper;
    private final ShiftTypeMapper shiftTypeMapper;
    private final WeekdayRuleMapper weekdayRuleMapper;

    @Transactional
    public void add(ShiftTeamRuleAddReqVo reqVo) {
        int day = (int) reqVo.getStart().until(reqVo.getEnd(), ChronoUnit.DAYS);
        day = day + 1;
        List<LocalDate> allDays = Stream.iterate(reqVo.getStart(), d -> d.plusDays(1))
                .limit(day)
                .collect(Collectors.toList());


        List<GroupWorkRule> ruleList = groupWorkRuleMapper.lambdaQuery()
                .eq(GroupWorkRule::getGroupId, reqVo.getGroupId())
                .in(GroupWorkRule::getDay, allDays)
                .list();

        Map<LocalDate, GroupWorkRule> ruleMap = ruleList.stream().collect(Collectors.toMap(GroupWorkRule::getDay, e -> e));

        Group group = groupMapper.selectById(reqVo.getGroupId());
        List<Shift> shiftList = shiftMapper.lambdaQuery().eq(Shift::getShiftTypeId, group.getShiftTypeId()).list();

        Map<Integer, ShiftTeamRuleAddDetailReqVo> collect = reqVo.getWorkAbilityList().stream().collect(Collectors.toMap(ShiftTeamRuleAddDetailReqVo::getShiftId, e -> e));

        for (LocalDate currentDay : allDays) {

            List<GroupWorkRule> list = groupWorkRuleMapper.lambdaQuery().eq(GroupWorkRule::getGroupId, reqVo.getGroupId()).eq(GroupWorkRule::getDay, currentDay).list();
            for (GroupWorkRule groupWorkRule : list) {
                groupWorkRuleMapper.deleteId(groupWorkRule.getId());
            }

            GroupWorkRule groupWorkRule = ruleMap.get(currentDay);
            if (groupWorkRule != null) {
                continue;
            }


//            groupWorkRule.setWorkAbility(reqVo.getWorkAbility());

            for (Shift shift : shiftList) {
                groupWorkRule = new GroupWorkRule();
                groupWorkRule.setGroupId(reqVo.getGroupId());
                groupWorkRule.setType(2);
                groupWorkRule.setDay(currentDay);
                groupWorkRule.setAttr(currentDay.toString());
                groupWorkRule.setShiftId(shift.getId());
                ShiftTeamRuleAddDetailReqVo shiftTeamRuleAddDetailReqVo = collect.get(shift.getId());
                groupWorkRule.setUserCount(shiftTeamRuleAddDetailReqVo.getUserCount());
                groupWorkRuleMapper.insert(groupWorkRule);
            }
        }

    }

    @Transactional
    public void remove(ShiftTeamRuleRemoveReqVo reqVo) {

        if(reqVo.getDay() != null){
            List<GroupWorkRule> list = groupWorkRuleMapper.lambdaQuery()
                    .eq(GroupWorkRule::getGroupId, reqVo.getGroupId())
                    .eq(GroupWorkRule::getDay, reqVo.getDay())
                    .list();
            for (GroupWorkRule groupWorkRule : list) {
                groupWorkRuleMapper.deleteId(groupWorkRule.getId());
            }

            return ;
        }

        LocalDate start = LocalDate.parse(reqVo.getMonth() + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        LocalDate end = start.plusMonths(1).minusDays(1);

        List<GroupWorkRule> list = groupWorkRuleMapper.lambdaQuery()
                .eq(GroupWorkRule::getGroupId, reqVo.getGroupId())
                .ge(GroupWorkRule::getDay, start)
                .le(GroupWorkRule::getDay, end)
                .list();
        for (GroupWorkRule groupWorkRule : list) {
            groupWorkRuleMapper.deleteId(groupWorkRule.getId());
        }

    }

    @Transactional(readOnly = true)
    public List<GroupWorkRuleFindShiftResVo> findShift(GroupWorkRuleFindShiftReqVo reqVo) {

        Group group = groupMapper.selectById(reqVo.getId());

        List<Shift> list = shiftMapper.lambdaQuery().eq(Shift::getShiftTypeId, group.getShiftTypeId()).list();

        if(CollUtil.isEmpty(list)){
            return new ArrayList<>();
        }

        List<GroupWorkRule> groupRule = groupWorkRuleMapper.lambdaQuery().eq(GroupWorkRule::getGroupId, reqVo.getId()).eq(GroupWorkRule::getDay, reqVo.getDay()).list();
        Map<Integer, GroupWorkRule> workRuleMap = groupRule.stream().collect(Collectors.toMap(GroupWorkRule::getShiftId, e -> e));

        List<GroupWorkRuleFindShiftResVo> resVo = list.stream()
                .map(e -> {
                    GroupWorkRuleFindShiftResVo resVo1 = new GroupWorkRuleFindShiftResVo();
                    BeanUtils.copyProperties(e, resVo1);
                    resVo1.setShiftId(e.getId());
                    resVo1.setUserCount(group.getUserCount());
                    if (reqVo.getDay() == null) {
                        resVo1.setUserDayCount(group.getUserCount());
                    } else {
                        GroupWorkRule groupWorkRule = workRuleMap.get(e.getId());
                        if (groupWorkRule != null) {
                            resVo1.setUserDayCount(groupWorkRule.getUserCount());
                        } else {
                            resVo1.setUserDayCount(group.getUserCount());
                        }
                    }
                    resVo1.setUserTimes(resVo1.getUserDayCount().divide(resVo1.getUserCount(), 2, RoundingMode.HALF_UP));
                    return resVo1;
                })
                .collect(Collectors.toList());

        List<Integer> ids = list.stream().map(BaseEntity::getId).distinct().collect(Collectors.toList());

        List<BreakPeriod> breakPeriods = breakPeriodMapper.lambdaQuery().in(BreakPeriod::getShiftId, ids).list();
        Map<Integer, List<ShiftListBreakPeriodResVo>> collect = breakPeriods.stream().map(e-> MkBeanUtils.copyProperties(e, ShiftListBreakPeriodResVo.class)).collect(Collectors.groupingBy(ShiftListBreakPeriodResVo::getShiftId));

        for (GroupWorkRuleFindShiftResVo shift : resVo) {
            List<ShiftListBreakPeriodResVo> breakPeriods1 = collect.get(shift.getShiftId());
            if(breakPeriods1 != null){
                shift.setBreakPeriods(breakPeriods1);
            } else {
                shift.setBreakPeriods(new ArrayList<>());
                ShiftListBreakPeriodResVo shiftListBreakPeriodResVo = new ShiftListBreakPeriodResVo();
                shiftListBreakPeriodResVo.setOffsetStartDay(0);
                shiftListBreakPeriodResVo.setStartTime(null);
                shiftListBreakPeriodResVo.setOffsetEndDay(1);
                shiftListBreakPeriodResVo.setEndTime(null);
                shift.getBreakPeriods().add(shiftListBreakPeriodResVo);
            }
        }

        return resVo;

    }

    @Transactional(readOnly = true)
    public List<GroupWorkRuleTimeRangeResVo> timeRange(GroupWorkRuleTimeRangeReqVo reqVo) {

        List<GroupWorkRule> groupRuleList = groupWorkRuleMapper.lambdaQuery()
                .eq(GroupWorkRule::getGroupId, reqVo.getId())
                .ge(GroupWorkRule::getDay, reqVo.getStart())
                .le(GroupWorkRule::getDay, reqVo.getEnd())
                .list();

        Map<LocalDate, List<GroupWorkRule>> ruleDayMap = groupRuleList.stream().collect(Collectors.groupingBy(GroupWorkRule::getDay));


        List<GroupWorkRuleTimeRangeResVo> allWeekday1 = Stream
                .iterate(reqVo.getStart(), s -> s.plusDays(1))
                .limit(reqVo.getStart().until(reqVo.getEnd(), ChronoUnit.DAYS) + 1)
                .map(e -> {
                    GroupWorkRuleTimeRangeResVo resVo = new GroupWorkRuleTimeRangeResVo();
                    resVo.setDay(e);
                    List<GroupWorkRule> ruleList1 = ruleDayMap.get(e);
                    if (ruleList1 != null) {
                        resVo.setDetails(ruleList1.stream().map(r -> {
                            GroupWorkRuleTimeRangeDetailResVo detailResVo = new GroupWorkRuleTimeRangeDetailResVo();
                            BeanUtils.copyProperties(r, detailResVo);
                            return detailResVo;
                        }).collect(Collectors.toList()));
                    }

                    return resVo;
                 })
                .collect(Collectors.toList());

        Map<LocalDate, GroupWorkRuleTimeRangeResVo> collect2 = allWeekday1.stream().collect(Collectors.toMap(GroupWorkRuleTimeRangeResVo::getDay, e -> e));

        Group group = groupMapper.selectById(reqVo.getId());

        ShiftType shiftType = shiftTypeMapper.selectById(group.getShiftTypeId());

        LocalDate startDate = reqVo.getStart().minusMonths(3);
        LocalDate endDate = shiftType.getEndDate();
        //  班次
        List<Shift> shifts = shiftMapper.selectList(Wrappers.<Shift>lambdaQuery().eq(Shift::getShiftTypeId, shiftType.getId()));
        if(shifts.isEmpty()){
            throw SyExceptionUtils.e("该方案没有班制，请先配置班制");
        }

        //  周计划
        List<WeekdayRule> ruleList = weekdayRuleMapper.selectList(Wrappers.<WeekdayRule>lambdaQuery().eq(WeekdayRule::getShiftTypeId, shiftType.getId()));
        if(ruleList.isEmpty()){
            throw SyExceptionUtils.e("该方案的周配置被删除，请联系管理员或者删除后重新添加");
        }
        //  生成日历信息
        List<WeekdaySchemeTimeRangeResVo> allWeekday = Stream
                .iterate(startDate, s -> s.plusDays(1))
                .limit(startDate.until(endDate, ChronoUnit.DAYS) + 1)
                .map(e -> {
                    WeekdaySchemeTimeRangeResVo newDay = new WeekdaySchemeTimeRangeResVo();
                    newDay.setShiftDay(e);

                    for (Shift shift : shifts) {
                        WeekdaySchemeTimeRangeItemResVo item  = new WeekdaySchemeTimeRangeItemResVo();
                        item.setShiftSeq(shift.getSeq());
                        item.setOffsetStartDay(shift.getOffsetStartDay());
                        item.setOffsetEndDay(shift.getOffsetEndDay());
                        item.setStartTime(shift.getStartTime());
                        item.setEndTime(shift.getEndTime());
                        item.setShiftId(shift.getId());
                        newDay.getItem().add(item);
                    }
                    return newDay;
                })
                .collect(Collectors.toList());

        // 匹配规则，可以抽成策略模式，数据量大，为了效率不建议
        for (WeekdaySchemeTimeRangeResVo w : allWeekday) {
            for (WeekdayRule weekdayRule : ruleList) {
                if (weekdayRule.getType() == 3) {
                    if (LocalDate.parse(weekdayRule.getValue(), DatePattern.NORM_DATE_FORMATTER).equals(w.getShiftDay())) {
                        w.setDateType(weekdayRule.getDateType());
                        w.setWorkState(weekdayRule.getStatus());
                        w.setText(weekdayRule.getText());
                    }


                } else if (weekdayRule.getType() == 1){
                    String value = weekdayRule.getValue();
                    String[] split = value.split(ProjectConstants.WEEKDAY_RULE_WEEKVALUE_SPLIT);
                    LocalDate start = LocalDate.parse(split[0], DatePattern.NORM_DATE_FORMATTER);
                    LocalDate end = LocalDate.parse(split[1], DatePattern.NORM_DATE_FORMATTER);
                    if (!w.getShiftDay().isBefore(start) && !w.getShiftDay().isAfter(end)) {
                        w.setDateType(weekdayRule.getDateType());
                        w.setWorkState(weekdayRule.getStatus());
                        w.setText(weekdayRule.getText());
                    }


                }else if (weekdayRule.getType() == 4) {
                    if (weekdayRule.getValue().equals(String.valueOf(w.getShiftDay().getDayOfWeek().getValue()))) {
                        w.setDateType(weekdayRule.getDateType());
                        w.setWorkState(weekdayRule.getStatus());
                    }
                } else if (weekdayRule.getType() == 2) {
                    String value = weekdayRule.getValue();
                    String[] split = value.split(ProjectConstants.WEEKDAY_RULE_WEEKVALUE_SPLIT);
                    if(w.getShiftDay().equals(LocalDate.parse(split[0], DatePattern.NORM_DATE_FORMATTER))){
                        w.getItem().stream().filter(e -> e.getShiftId().equals(Integer.valueOf(split[1]))).findFirst().ifPresent(e -> {
                            e.setWorkState(weekdayRule.getStatus());
                            e.setText(weekdayRule.getText());
                        });
                    }
/*

                    w.setDateType(weekdayRule.getDateType());
                    w.setWorkState(weekdayRule.getStatus());
                    w.getItem().stream().filter(e->e.getShiftId().equals(Integer.valueOf(split[1]))).findFirst().ifPresent(e->{
                        e.setWorkState(weekdayRule.getStatus());
                        e.setText(weekdayRule.getText());
                    });
*/

                }
            }
        }

        //  每天上下班情况
        Map<LocalDate, WeekdaySchemeTimeRangeResVo> collect1 = allWeekday.stream().collect(Collectors.toMap(WeekdaySchemeTimeRangeResVo::getShiftDay, e -> e));

        // 获取日内计划
        List<WeekdaySchemeTimeRangeResVo> resVoList = Stream.iterate(reqVo.getStart(), e -> e.plusDays(1L))
                .limit(reqVo.getStart().until(reqVo.getEnd(), ChronoUnit.DAYS) + 1)
                .filter(collect1::containsKey)
                .map(e -> {
                    WeekdaySchemeTimeRangeResVo r = new WeekdaySchemeTimeRangeResVo();
                    r.setShiftDay(e);
                    WeekdaySchemeTimeRangeResVo weekdaySchemeTimeRangeResVo = collect1.get(e);
                    r.setWorkState(weekdaySchemeTimeRangeResVo.getWorkState());
                    r.setText(weekdaySchemeTimeRangeResVo.getText());
                    r.setDateType(weekdaySchemeTimeRangeResVo.getDateType());
                    List<WeekdaySchemeTimeRangeItemResVo> collect =new LinkedList<>();
                    List<WeekdaySchemeTimeRangeItemResVo> items = weekdaySchemeTimeRangeResVo.getItem();
                    for(WeekdaySchemeTimeRangeItemResVo item: items){
                        if(!ObjectUtils.isEmpty(item.getWorkState()) && !item.getWorkState().equals(r.getWorkState())){
                            collect.add(item);
                        }
                    }
                    r.setItem(collect);


                    GroupWorkRuleTimeRangeResVo resVo = collect2.get(e);
                    resVo.setWorkState(r.getWorkState());
                    resVo.setText(r.getText());
                    resVo.setDateType(r.getDateType());
                    resVo.setItem(r.getItem());

                    return r;
                })
                .collect(Collectors.toList());

        return allWeekday1;


    }
}
